<?php

namespace App\Repositories;

use App\Http\Controllers\Api\Mock\MockContact;
use App\Models\AccessToken;
use App\Models\ApiLog;
use EasyWeChat\Factory;
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
use EasyWeChat\Kernel\Support\Collection;
use Exception;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;


/**
 * @method sendMessage(array $array)
 */
class WechatRepository
{
    public $easyWeChat;
    private $Tencent_Api_Prefix;
    private $Tencent_Api_token;
    private $Tencent_Api_Secret;
    private $config;
    public $sendData;

    public function __construct($config = [])
    {
        $this->config = [
            'app_id' => $config['app_id'] ?? env('WECHAT_MINIAPP_ID'),
            'secret' => $config['app_secret'] ?? env('WECHAT_MINIAPP_SECRET'),
            'mch_id' => env('WECHAT_MINIAPP_MCH_ID'),
            'key' => env('WECHAT_MINIAPP_MCH_key'),
            'notify_url' => env('WECHAT_MINIAPP_NOTIFY_URL'),
            // 指定 API 调用返回结果的类型：array(default)/collection/object/raw/自定义类名
            'response_type' => 'array',
            'log' => [
                'default' => 'dev', // prod
                'channels' => [
                    'dev' => [
                        'driver' => 'single',
                    ],
                    'prod' => [
                        'driver' => 'daily',
                    ],
                ],
                // "file" => "/opt/logs/easywechat.log"
            ],
            'http' => [
                'max_retries' => 1,
                'retry_delay' => 500,
                'timeout' => 5.0,
            ],
        ];

        $this->easyWeChat = Factory::miniProgram($this->config);
    }

    /**
     * 获取小程序 ShortLink 如：#小程序://小程序示例/示例页面/9pZvnVw3KMCQpVp
     *
     * @param string $pageUrl 通过 Short Link 进入的小程序页面路径，必须是已经发布的小程序存在的页面，可携带 query，最大1024个字符
     * @param string $pageTitle 页面标题，不能包含违法信息，超过20字符会用... 截断代替
     * @param bool $isPermanent 默认值false。生成的 Short Link 类型，短期有效：false，永久有效：true
     */
    public function getShortLink(string $pageUrl, string $pageTitle, bool $isPermanent = false)
    {
        return $this->easyWeChat->short_link->getShortLink($pageUrl, $pageTitle, $isPermanent);
    }

    /**
     * [getUserPhoneNumber 获取手机号]
     * @Author      TomJent
     * @Date        2023-02-08
     * @Domian      微信文档：https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/phonenumber/phonenumber.getPhoneNumber.html
     *
     * $code [手机号获取凭证]
     */
    public function getUserPhoneNumber(string $code)
    {
        return $this->easyWeChat->phone_number->getUserPhoneNumber($code);
    }

    /**
     * 登录微信api，确认身份
     *
     * @param string $code
     *
     * @return array
     */
    public function toSession(string $code)
    {
        return $this->easyWeChat->auth->session($code);
    }

    /**
     * 登录微信api，确认身份
     *
     * @param array $wxSession
     * @param string $iv
     * @param string $data
     *
     * @return array
     */
    public function decodeUserInfo(array $wxSession, string $iv, string $data): array
    {
        return $this->easyWeChat->encryptor->decryptData($wxSession['session_key'], $iv, $data);
    }

    /**
     * 获取限量二维码，适用于长期用，比如扫码
     *
     * @param string $url 小程序码链接
     * @param array $param 扩展参数
     * @param bool $returnContent 是否返回图片内容
     */
    public function limitedQr(string $url, array $param = [], bool $returnContent = true)
    {
        $response = $this->easyWeChat
            ->app_code
            ->get($url, $param);
        if ($returnContent) {
            return $response->getBody()->getContents();
        }

        return $response;
    }

    /**
     * 获取无限量二维码，适用于登录
     * @param string $url 小程序码链接
     * @param array $param 扩展参数
     * @param bool $returnContent 是否返回图片内容
     */
    public function unLimitedQr($scene, $url, $param = [], $returnContent = true)
    {
        $param['page'] = $url;

        $response = $this->easyWeChat
            ->app_code
            ->getUnlimit(
                $scene,
                $param
            );

        if ($returnContent) {
            return $response->getBody()->getContents();
        } else {
            return $response;
        }
    }

    /**
     * 推广二维码
     * @Author   thomashtao               bosslen
     * @DateTime 2022-09-01T11:25:25+0800
     * @param string $value [description]
     * @return   [type]                           [description]
     */
    public function spreadQr($uid = '')
    {
        /*
        if (isProdEnv()) {
            return $this->unLimitedQr($uid, '/pages/elect-pool/my', ['width' => 300]);
        }
        */
//        return $this->limitedQr('/pages/elect-pool/my?share_id=' . $uid, ['width' => 200]);
        return $this->limitedQr('/pages/public/spread?share_id=' . $uid, [
            'width' => 200,
            'is_hyaline' => false
        ]);
    }

    //生成互选池二维码
    public function electPoolQr($eid = '', $is_hyaline = false, $param = [], $content = "")
    {
        $url = '/pages/elect-pool/elect?id=' . $eid;
        foreach ($param as $k => $v) {
            $url .= $k . '=' . $v . '&';
        }
        $pic = $this->limitedQr($url, [
            'width' => 300,
            'is_hyaline' => $is_hyaline
        ]);

        if (!$pic) return "";

        return (new FileRepository())->uploadQrcodeToOss($pic, "elect_pool_" . $eid, null, "elect_pool", $content);
    }

    /**
     * 生成通用二维码
     * @Author   thomashtao               bosslen
     * @DateTime 2022-10-04T16:40:26+0800
     * @param    [type]                   $url    [description]
     * @param array $data [description]
     * @param integer $type [description]
     * @return   [type]                           [description]
     *
     * scane  9001: 推送
     */
    public function createQr($url, $data = [], $type = 0)
    {
        if (empty($url))
            throw new Exception("链接地址必填");

        $scane = $data['scane']; //场景码
        unset($data['scane']);

        if ($type) {
            $data['width'] = 300;
            $pic = $this->unLimitedQr($scane ?? 100, $url, $data);
        } else {
            $query = [];
            foreach ($data as $k => $v) {
                $query[] = $k . '=' . $v;
            }
            $query = implode("&", $query);
            $fileName = md5($query);
            $query && $query = "?" . $query;
            $pic = $this->limitedQr($url . $query, ['width' => 300]);
        }

        if (!$pic) return "";

        return (new FileRepository())->uploadQrcodeToOss($pic, $fileName ?? md5(microtime()));
    }


    /**
     * 生成活动场次的小程序二维码
     *
     * @param $sessionId
     *
     * @return string
     */
    public function sessionQr($sessionId): string
    {
        if (isProdEnv()) {
            return $this->unLimitedQr($sessionId, 'pages/sign-list/index', ['width' => 600]);
        }

        return $this->limitedQr('/pages/sign-list/index?scene=' . $sessionId, ['width' => 600]);
    }

    /**
     * 发送模板消息
     *
     * @param string $templateId
     * @param string $toUserOpenId
     * @param string $page
     * @param array $data
     *
     * @return array|Collection|object|\Psr\Http\Message\ResponseInterface|string
     * @throws GuzzleException
     * @throws InvalidArgumentException
     * @throws InvalidConfigException
     */
    public function sendSubscribeMessage(string $templateId, string $toUserOpenId, string $page, array $data)
    {
        $sendData = [
            'template_id' => $templateId,
            'touser' => $toUserOpenId,
            'page' => $page,
            'data' => []
        ];

        foreach ($data as $k => $v) {
            $sendData['data'][$k] = [
                'value' => $v
            ];
        }
        $this->sendData = $sendData;

        return $this->easyWeChat->subscribe_message->send($sendData);
    }

    /**
     * 发送公众号模板消息
     *
     * @param string $templateId
     * @param string $toUserOpenId
     * @param string $page
     * @param array $data
     *
     * @return array|Collection|object|\Psr\Http\Message\ResponseInterface|string
     * @throws GuzzleException
     * @throws InvalidArgumentException
     * @throws InvalidConfigException
     */
    public function sendTemplateMessage(string $templateId, string $toUserOpenId, array $data, string $page)
    {

        $sendData = [
            'template_id' => $templateId,
            'touser' => $toUserOpenId,
            'url' => "",
            'miniprogram' => [
                'appid' => env('WECHAT_MINIAPP_ID'),//小程序id
                // 'path' => "pages/" . $page,//跳转页面 //pagepath
            ],
            'data' => $data
        ];

        $page && $sendData['miniprogram']['pagepath'] = "pages/" . $page;
        $this->sendData = $sendData;

        $accessToken = AccessToken::where("appid", "=", $this->config['app_id'])->first();
        $cache_token = $this->easyWeChat->access_token->getToken();
        if ($accessToken) {
            if (time() >= $accessToken['access_time'] + 7200) {
                AccessToken::where("id", "=", $accessToken['id'])->update([
                    'access_token' => $cache_token['access_token'],
                    'access_time' => time()
                ]);
            } else {
                if ($cache_token['access_token'] != $accessToken->access_token) {
                    $this->easyWeChat->access_token->setToken($accessToken->access_token);
                }
            }
        } else {
            AccessToken::where("id", "=", $accessToken['id'])->update([
                'access_token' => $cache_token['access_token'],
                'access_time' => time()
            ]);
        }

        $this->easyWeChat = Factory::officialAccount($this->config);
        return $this->easyWeChat->template_message->send($sendData);
    }

    /**
     * 生成签名
     *
     * @param string $ticket
     * @param int $timestamp
     * @param string $secret
     *
     * @return string
     */
    public function signBuild(string $ticket, int $timestamp)
    {
        return hash('sha256', $ticket . $this->Tencent_Api_token . $timestamp . $this->Tencent_Api_Secret);
    }

    /**
     * 获取签名（微保通知）
     * @param array $arrData 签名数组
     * @param string $method 签名方法
     * @return boolean|string 签名值
     */
    public function getSignature($arrData, $method = "sha1")
    {
        if (!function_exists($method)) {
            return false;
        }
        $paramString = '';
        $paramString .= $arrData['timestamp'] . $arrData['nonce'] . $arrData['token'];
        return $method($paramString);
    }

    /**
     * 退款
     * @Author   thomashtao               bosslen
     * @DateTime 2022-04-14T00:11:50+0800
     * @param    [type]                   $data   [description]
     * @return   [type]                           [description]
     */
    public function refund($data)
    {
        if (!$data['transaction_id'])
            throw new Exception("交易号不能为空", 1);

        if (!$data['refund_no'])
            throw new Exception("退款单号必填", 1);

        if (!$data['total_fee'])
            throw new Exception("总金额必填", 1);

        if (!$data['refund_fee'])
            throw new Exception("退款金额必填", 1);

        if ($data['total_fee'] < $data['refund_fee'])
            throw new Exception("退款金额不能大于总金额", 1);

        $this->config['cert_path'] = request()->server('DOCUMENT_ROOT') . "/cert/apiclient_cert.pem";
        $this->config['key_path'] = request()->server('DOCUMENT_ROOT') . "/cert/apiclient_key.pem";
        $app = Factory::payment($this->config);

        $other['refund_desc'] = '押金退还';
        $other['notify_url'] = ""; //退款回调地址

        return $app->refund->byTransactionId($data['transaction_id'], $data['refund_no'], $data['total_fee'] * 100, $data['refund_fee'] * 100, $other);
    }

    public function unifiedOrder($data)
    {
        $app = Factory::payment($this->config);
        if (!$data['body'])
            throw new Exception("Body必填", 1);

        if (!$data['out_trade_no'])
            throw new Exception("订单号必填", 1);

        if (!$data['openid'])
            throw new Exception("openid必填", 1);

        try {
            $uni_data = [
                'body' => $data['body'],
                'out_trade_no' => $data['out_trade_no'],
                'total_fee' => floatval($data['total_price']) * 100,
                'trade_type' => 'JSAPI', // 请对应换成你的支付方式对应的值类型
                'openid' => $data['openid'],
            ];

            $result = $app->order->unify($uni_data);
            if ($result['return_code'] == 'FAIL') {
                throw new Exception($result['return_msg'], 1);
            }

            $pay_data = $app->jssdk->sdkConfig($result['prepay_id']);

            return $pay_data;
        } catch (Exception $e) {
            dd($e->getMessage());
            throw new Exception($e->getMessage());
        }

    }

    /**
     * 支付异步回调
     * @Author   thomashtao               bosslen
     * @DateTime 2022-03-28T19:36:52+0800
     * @param    [type]                   $data   [description]
     * @return   [type]                           [description]
     */
    public function handleWxNotify()
    {
        try {
            DB::beginTransaction();
            $app = Factory::payment($this->config);
            $response = $app->handlePaidNotify(function ($message, $fail) {
                //建议在这里调用微信的【订单查询】接口查一下该笔订单的情况，确认是已经支付
                if ($message['return_code'] === 'SUCCESS') {
                    // 用户是否支付成功
                    if ($message['result_code'] === 'SUCCESS') {
                        $orderRep = new OrderRepository();
                        $orderRep->updateOrder($message['transaction_id'], $message['out_trade_no'], false, $message);
                        // 用户支付失败
                    } elseif ($message['result_code'] === 'FAIL') {
                        $order->pay_status = 4;
                    }
                } else {
                    Log::error($message);
                    return true;
                }

                DB::commit();
                return true; //返回处理完成
            });

            $response->send(); //return $response;

        } catch (Exception $e) {
            DB::rollBack();
            return false;
        }
    }

    /**
     * 提现
     * @Author   thomashtao               bosslen
     * @DateTime 2022-08-01T16:55:06+0800
     * @param float $price [description]
     * @return   [type]                           [description]
     *
     * "return_code" => "SUCCESS"
     * "return_msg" => null
     * "mch_appid" => "wx0842f4f0457b62b8"
     * "mchid" => "1488512332"
     * "nonce_str" => "62e7a2bb621ae"
     * "result_code" => "SUCCESS"
     * "partner_trade_no" => "wx20220801055403"
     * "payment_no" => "10100484292062208012922238111933"
     * "payment_time" => "2022-08-01 17:54:04"
     */
    public function withdraw($mch_billno = "", $openid = "", $price = 0.00)
    {
        if (!$openid) throw new Exception("openid 异常", 1);

        try {

            $data = [
                'partner_trade_no' => $mch_billno,   // 商户订单号，需保持唯一性(只能是字母或者数字，不能包含有符号)
                'openid' => $openid,                 //用户在平台下的openid,取决于appid
                'check_name' => 'NO_CHECK',       // NO_CHECK：不校验真实姓名
                're_user_name' => "",       // 如果 check_name 设置为FORCE_CHECK，则必填用户真实姓名
                'amount' => abs($price) * 100,        // 企业付款金额，单位为分
                'desc' => "推广返佣提现"               //企业付款操作说明信息。必填
            ];
            $this->config['cert_path'] = request()->server('DOCUMENT_ROOT') . "/cert/apiclient_cert.pem";
            $this->config['key_path'] = request()->server('DOCUMENT_ROOT') . "/cert/apiclient_key.pem";
            $res = Factory::payment($this->config)->transfer->toBalance($data);

            if ($res['result_code'] == 'SUCCESS')
                return ['status' => 1];
            else
                return ['status' => 0, 'msg' => $res['err_code_des']];

        } catch (Exception $e) {
            throw new Exception($e->getMessage(), 1);
        }

    }

}
